Faults and the FaultContract Attribute
Any given operation
can encounter an unexpected error at any time. Errors can be either
logic or infrastructure-related. In .NET, errors are trapped in the try-catch block and handled based on the error type. For example:
Example 27.
try { ... try something ... } catch (Exception e) { ... do something ... }
|
Service-orientation
stipulates service autonomy. Therefore, a service should not have to
depend on the service consumer to handle the error and recover from it.
Conversely, the service consumer is decoupled from the service
implementation and does not need to understand
its implementation details. Local technology-specific error messages
may not provide any value to the service consumer and may even
represent a security threat when error messages contain private details
about the service implementation.
It therefore is not
recommended to pass technology-specific errors as SOAP Faults. Error
notifications should not be part of the contract between the service
and its consumers. If a service fails on an operation call, it should
ideally not have an impact on the service consumers.
WCF does not, by
default, return unhandled exceptions as SOAP Faults. To return an
actual SOAP Fault, the service needs to throw a generic type FaultException<T> where T is a data contract that defines the fault.
This is also known as a fault contract, which can look like this:
Example 28.
[DataContract] public class ServiceFault { [DataMember] public string OperationName; [DataMember] public string ExceptionMessage; }
|
A service defines a fault contract by applying the FaultContract attribute, which results in the ServiceFault data contract being published with the schema:
Example 29.
[ServiceContract] public interface IGreetings { [OperationContract] [FaultContract(typeof(ServiceFault))] string Greet(); }
|
Greetings is a service-type implementation for IGreetings. When the service traps an error in the Greet() method, it creates an instance of the ServiceFault class and populates the OperationName and ExceptionMessage attributes. The fault contract is then marshaled to the service consumer:
Example 30.
class Greetings : IGreetings { public string Greet() { try { return "Hello World"; } catch (Exception ex) { ServiceFault fault = new ServiceFault(); fault.OperationName = "Greet"; fault.ExceptionMessage = ex.Message; throw new FaultException<ServiceFault>(fault); } } }
|
The service consumer will then need to write an appropriate catch statement to manage exceptions:
Example 31.
try { string response = proxy.Greet(); Console.WriteLine(response); } catch(FaultException<ServiceFault> ex) { Console.WriteLine("Fault:" + ex.Detail.OperationName.ToString() + " -" + ex.Detail.ExceptionMessage.ToString()); }
|
MEX Endpoints
In order to consume a
service, the service consumer needs to import the service contract and
generate a proxy class. However, the service consumer may first need to
locate the service metadata in order to do so. To enable this level of
service discovery, the service makes its metadata available and
accessible via industry standard protocols.
When we use the term “metadata” here, we are referring to the familiar set of service contract-related documents, including:
Metadata exchange (MEX) is a
mechanism based on the WS-MetadataExchange specification and designed
for the online retrieval of service metadata by service consumers. This
is accomplished by having the service expose an MEX endpoint. As illustrated in Figure 5.10, the service responds to an MEX request by returning an MEX response message containing the requested metadata.
A WCF service can expose an
MEX endpoint as long as the service registers at least one of TCP,
HTTP, or IPC base addresses in the service endpoint. An MEX endpoint
has its own address and is explicitly made available by the service
owner.
Metadata for a service is
published to the service address with “/MEX” appended to it. For
example, if the service address is
“http://www.example.org:322/acconts.svc,” the corresponding MEX address
would be “http://www.example.org:322/acconts.svc/mex.”
Versioning Considerations
Over
its lifetime, a service may need to be augmented for any number of
reasons, including in response to changes in hosting infrastructure.
Some types of changes can be kept internal to the underlying service
logic and implementation, whereas others may impact the actual service
contract.
For example:
the service’s address may need to be changed if the service implementation is moved to a new physical location
the
service’s binding information may need to change if the service is
forced to support a different transport (with perhaps different
security settings)
the
published service contract itself will likely be changed if
modifications are required to the operations it has been exposing
As advocated by the Service Abstraction
principle, to whatever extent possible, changes to a given service
contract should be hidden from service consumers, so that services and
service consumers can be versioned and evolved independently, with
minimal impact on each other. In some cases it may be necessary to
apply Concurrent Contracts so that a given service publishes multiple versions of its data contract.
Other patterns that relate directly to contrct versioning include Compatible Change , Version Identification , and Terminiation Notification .
|
There are three established versioning strategies:
Strict Strategy (new change, new contract)
Flexible Strategy (backwards compatibility)
Loose Strategy (backwards and forwards compatibility)
Each strategy approaches
versioning in a different way, based on requirements and the measure of
“impact control” you need to exercise over your service contracts.
Data contracts are one of several attributes of a service that must be versioned for a service.
In
support of the Flexible Strategy where the emphasis is on preserving
backwards compatibility, backwards compatible changes can be
implemented in the service endpoint by not altering the name and
namespace of established data contracts and data members. Instead, if
new name and namespace values are required, the original values can be
preserved using the name and namespace parameters feature, as follows:
Example 32.
[DataContract (Namespace = "Finance", Name = "AccountContract")] [DataMember(Name = "Total")]
|
The data types returned by
the data members and the order in which the data members are presented
are not modified, and newly added data members appear after existing
data members.
It is generally not
recommended to use parameters initially. When a second version is
required, all new operations can have the order parameter set to 2. The
version after that will have the order parameter for all new data
members set to 3, and so on. Note that changing any property of
existing data members, such as the IsRequired property, will result in a non-backwards-compatible change.
Note
Previous versions of .NET
supported generating CLR code from XML Schema and the generation of XML
schemas from CLR types using the XML Schema Definition Tool (XSD.exe).
With data contracts, this functionality is incorporated into the CLR
and optimized for performance.
The WCF service contract represents the technical interface of a WCF service. The
interface contract expresses the abstract part of a service contract,
and is comprised of operation contracts, message contracts, and data
contracts. The
service endpoint expresses the concrete part of a service contract by
establishing the wire-level details for invoking the service. WCF does not support SOAP faults by default, but they can be programmatically defined. An MEX endpoint enables service consumers to dynamically retrieve metadata about a corresponding service. There are different versioning strategies that can be used to evolve service contracts. WCF includes support for REST based services using WebGet, WebInvoke and several other classes in the System.ServiceModel.Web assembly.
|